package com.ppl.server.boot;

import com.google.common.io.Resources;
import com.google.inject.ProvisionException;

import com.ppl.server.boot.config.*;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.http.server.ServerConfiguration;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import org.glassfish.grizzly.ssl.SSLContextConfigurator;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.grizzly.threadpool.ThreadPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.core.UriBuilder;
import java.io.File;
import java.net.URI;


/**
 * To enable an HTTP connector, it has to be created as an eager singleton with
 * Guice.
 * <p/>
 * The server is configured and optionally started using the
 * {@link com.ppl.server.modules.LifecycleModule}, thus the module has to be used in the injector's
 * context.
 * <p/>
 * The configuration of the server is provided through the {@link com.ppl.server.modules.ConfigModule}.
 *
 * @author Nicolas Berthet
 * @since May 29, 2012 10:28:53 AM
 */
public abstract class HttpConnector {
  private Logger log = LoggerFactory.getLogger(getClass());

  @Config("http")
  private HttpServerConfig serverConfig;

  private HttpServer httpServer;

  /**
   * Whether the HTTP server should be start automatically on startup and
   * stopped on shutdown.
   */
  private boolean autoStart;

  public HttpConnector() {
    this(true);
  }

  public HttpConnector(boolean autoStart) {
    this.autoStart = autoStart;
  }

  @Startup
  public void startup() throws Exception {
    configure();

    if (autoStart) {
      httpServer.start();
    }
  }

  @Shutdown
  public void shutdown() {
    if (autoStart && httpServer != null) {
      httpServer.stop();
    }
  }

  private void configure() {
    httpServer = new HttpServer();
    // Map the path to the processor.

      ServerConfiguration conf = httpServer.getServerConfiguration();
      int counter = 0;
      for (ListenPointConfig singleListenPointConfig : serverConfig.getListenPoints()) {
          boolean enableSSL = singleListenPointConfig.getSslConfig() != null && singleListenPointConfig.getSslConfig().isEnabled();
          final String uriPrefix = enableSSL ? "https://" : "http://";
          URI baseUri = UriBuilder.fromUri(uriPrefix + singleListenPointConfig.getAddress() + "/").port(singleListenPointConfig.getPort()).build();

          log.info("Starting HTTP listen point service on {} {} SSL support", baseUri, enableSSL ? "with" : "without");

          NetworkListener listener = new NetworkListener("grizzly" + counter, singleListenPointConfig.getAddress(), singleListenPointConfig.getPort());

          configureTransport(listener.getTransport(), singleListenPointConfig);

          if (enableSSL) {
              configureSsl(listener, singleListenPointConfig.getSslConfig());
          }

          httpServer.addListener(listener);
          HttpHandler handler = createHttpHandler();
          if (handler != null) {
              conf.addHttpHandler(handler, baseUri.getPath());
          }
          counter++;
      }

  }

  /**
   * Creates an HttpHandler to handle requests from this http connector.
   *
   * @return
   */
  protected abstract HttpHandler createHttpHandler();

  /**
   * Configures the TCP transport. The default implementation defines the
   * worker thread pool size.
   */
  protected void configureTransport(TCPNIOTransport transport, ListenPointConfig config) {

    ThreadPoolConfig networkThreads = ThreadPoolConfig
        .defaultConfig()
        .setCorePoolSize(config.getThreadPool().getCorePoolSize())
        .setMaxPoolSize(config.getThreadPool().getMaxPoolSize());

    transport.setWorkerThreadPoolConfig(networkThreads);
  }

  /**
   * Configures the SSL context of the server using the configuration provided.
   */
  protected void configureSsl(NetworkListener networkListener, SSLConfig ssl) {
    SSLContextConfigurator sslContextConfigurator = new SSLContextConfigurator(true);

    try {
      File keystoreFile = new File(Resources.getResource(ssl.getKeystoreFile()).toURI());
      sslContextConfigurator.setKeyStoreFile(keystoreFile.getAbsolutePath());
    } catch (Exception exception) {
      throw new ProvisionException("SSL Configuration failed, KeyStore file not found", exception);
    }

    sslContextConfigurator.setKeyStorePass(ssl.getKeystorePassword());

    try {
      File truststoreFile = new File(Resources.getResource(ssl.getTruststoreFile()).toURI());
      sslContextConfigurator.setTrustStoreFile(truststoreFile.getAbsolutePath());
    } catch (Exception exception) {
      throw new ProvisionException("SSL Configuration failed, TrustStore file not found", exception);
    }

    sslContextConfigurator.setTrustStorePass(ssl.getTruststorePassword());

    if (!sslContextConfigurator.validateConfiguration(true)) {
      throw new ProvisionException("Invalid SSL configuration, fix the configuration or deactivate SSL.");
    }

    SSLEngineConfigurator sslConfig = new SSLEngineConfigurator(sslContextConfigurator);
    sslConfig.setNeedClientAuth(false);
    sslConfig.setWantClientAuth(false);
    sslConfig.setClientMode(false);

    networkListener.setSecure(ssl.isEnabled());
    networkListener.setSSLEngineConfig(sslConfig);
  }
}
